﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using OfficeOpenXml;
using Xenta.Attributes;
using Xenta.Utils;
using System.Text;

namespace Xenta.Plugins
{
    [Dependency("7983070d-1df8-4c17-8ed6-1bd87689645f", 
        Name = "XML Import/Export Format")]
    public sealed class XmlImportExportFormat :  IImportExportFormat
    {
        #region Properties

        public string MimeType
        {
            get
            {
                return "text/xml";
            }
        }

        #endregion

        #region Methods

        public IEnumerable<IDictionary<String, String>> Read(Stream stream)
        {
            var xDoc = XDocument.Load(stream);
            var xData = xDoc.Element("data");
            if(xData == null)
                return Enumerable.Empty<IDictionary<String, String>>();
            return xData.Elements("object")
                .Select(o => o.Attributes()
                .ToDictionary(a => a.Name.LocalName, a => a.Value))
                .ToList();
        }

        public void Write(Stream stream, IEnumerable<IDictionary<String, String>> collection)
        {
            var xDoc = new XDocument(new XDeclaration("1.0", "utf-8", "yes"));
            var xData = new XElement("data");
            foreach(var obj in collection)
            {
                var xObject = new XElement("object");
                foreach(var property in obj)
                    xObject.SetAttributeValue(property.Key, property.Value);
                xData.Add(xObject);
            }
            xDoc.Add(xData);
            xDoc.Save(stream);
        }

        #endregion
    }

    [Dependency("ddbf933e-6df3-4f8c-9464-1d4b28cd4d22", 
        Name = "CSV(comma) Import/Export Format")]
    public sealed class CsvImportExportFormat : IImportExportFormat
    {
        #region Properties

        public string MimeType
        {
            get
            {
                return "text/csv";
            }
        }

        #endregion

        #region Methods

        public IEnumerable<IDictionary<String, String>> Read(Stream stream)
        {
            var ret = new List<IDictionary<String, String>>();
            using(var reader = new StreamReader(stream, Encoding.UTF8, true, 1024, false))
            {
                string headerStr = reader.ReadLine();
                if(String.IsNullOrWhiteSpace(headerStr))
                    return ret;
                var headerFields = CsvHelper.Split(headerStr);
                string lineStr = String.Empty;
                while((lineStr = reader.ReadLine()) != null)
                {
                    var lineFields = CsvHelper.Split(lineStr);
                    if(headerFields.Length != lineFields.Length)
                        continue;
                    var obj = new Dictionary<String, String>();
                    for(int i = 0; i < lineFields.Length; i++)
                        obj.Add(headerFields[i], lineFields[i]);
                    ret.Add(obj);
                }
            }
            return ret;
        }

        /// <summary>
        /// TODO: We don't dispose the StreamWriter here, since we should not close the underlying stream. 
        /// TODO: In .NET 4.5 there an argument for the StreamWriter constructor - leaveOpen. When we will use .NET 4.5, 
        /// TODO: we should rewrite this method.
        /// </summary>
        public void Write(Stream stream, IEnumerable<IDictionary<String, String>> collection)
        {
            var headerFields = collection.SelectMany(x => x.Keys).Distinct().ToArray();
            using(var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true))
            {
                writer.WriteLine(CsvHelper.Merge(headerFields));
                foreach(var obj in collection)
                {
                    var lineFields = new string[headerFields.Length];
                    for(int i = 0; i < headerFields.Length; i++)
                    {
                        string key = headerFields[i];
                        if(obj.ContainsKey(key))
                            lineFields[i] = obj[key];
                        else
                            lineFields[i] = String.Empty;
                    }
                    writer.WriteLine(CsvHelper.Merge(lineFields));
                }
            }
        }

        #endregion
    }

    [Dependency("34f1dde5-73a3-44b4-929a-cc1a3f5ce212", 
        Name = "CSV(semicolon) Import/Export Format")]
    public sealed class CsvImportExportFormat2 : IImportExportFormat
    {
        #region Properties

        public string MimeType
        {
            get
            {
                return "text/csv";
            }
        }

        #endregion

        #region Methods

        public IEnumerable<IDictionary<String, String>> Read(Stream stream)
        {
            var ret = new List<IDictionary<String, String>>();
            using(var reader = new StreamReader(stream, Encoding.UTF8, true, 1024, false))
            {
                string headerStr = reader.ReadLine();
                if(String.IsNullOrWhiteSpace(headerStr))
                    return ret;
                var headerFields = CsvHelper.Split(headerStr, ';');
                string lineStr = String.Empty;
                while((lineStr = reader.ReadLine()) != null)
                {
                    var lineFields = CsvHelper.Split(lineStr, ';');
                    if(headerFields.Length != lineFields.Length)
                        continue;
                    var obj = new Dictionary<String, String>();
                    for(int i = 0; i < lineFields.Length; i++)
                        obj.Add(headerFields[i], lineFields[i]);
                    ret.Add(obj);
                }
            }
            return ret;
        }

        public void Write(Stream stream, IEnumerable<IDictionary<String, String>> collection)
        {
            var headerFields = collection.SelectMany(x => x.Keys).Distinct().ToArray();
            using(var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true))
            {
                writer.WriteLine(CsvHelper.Merge(headerFields, ';'));
                foreach(var obj in collection)
                {
                    var lineFields = new string[headerFields.Length];
                    for(int i = 0; i < headerFields.Length; i++)
                    {
                        string key = headerFields[i];
                        if(obj.ContainsKey(key))
                            lineFields[i] = obj[key];
                        else
                            lineFields[i] = String.Empty;
                    }
                    writer.WriteLine(CsvHelper.Merge(lineFields, ';'));
                }
            }
        }

        #endregion
    }

    [Dependency("7b3f479a-bc21-43e9-9651-982956db67ea",
        Name = "Excel(XSLX) Import/Export Format")]
    public sealed class ExcelImportExportFormat : IImportExportFormat
    {
        #region Properties

        public string MimeType
        {
            get
            {
                return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
            }
        }

        #endregion

        #region Methods

        public IEnumerable<IDictionary<String, String>> Read(Stream stream)
        {
            var ret = new List<IDictionary<String, String>>();
            using(var pkg = new ExcelPackage())
            {
                pkg.Load(stream);

                var ws = pkg.Workbook.Worksheets["data"];
                if(ws == null)
                    return ret;

                int row = 0;
                var header = new List<String>();

                for(int i = 0; i < ws.Dimension.End.Column; i++)
                {
                    string value = ws.Cells[row + 1, i + 1].GetValue<String>();
                    if(String.IsNullOrWhiteSpace(value))
                        break;
                    header.Add(value);
                }
                row++;

                while(row < ws.Dimension.End.Row)
                {
                    var obj = new Dictionary<String, String>();
                    for(int i = 0; i < header.Count; i++)
                    {
                        string value = ws.Cells[row + 1, i + 1].GetValue<String>();
                        value = StringHelper.EnsureNotNull(value);
                        obj.Add(header[i], value);
                    }
                    if(obj.Values.All(String.IsNullOrWhiteSpace))
                        break;
                    ret.Add(obj);
                    row++;
                }
            }
            return ret;
        }

        public void Write(Stream stream, IEnumerable<IDictionary<String, String>> collection)
        {
             using(var pkg = new ExcelPackage())
             {
                 var ws = pkg.Workbook.Worksheets.Add("data");
                 int row = 0;
                 var header = collection.SelectMany(x => x.Keys).Distinct().ToArray();

                 for(int i = 0; i < header.Length; i++)
                     ws.Cells[row + 1, i + 1].Value = header[i];
                 row++;

                 foreach(var obj in collection)
                 {
                     for(int i = 0; i < header.Length; i++)
                     {
                         string key = header[i];
                         if(obj.ContainsKey(key))
                             ws.Cells[row + 1, i + 1].Value = obj[key];
                         else
                             ws.Cells[row + 1, i + 1].Value = String.Empty;
                     }
                     row++;
                 }

                 pkg.SaveAs(stream);
             }
        }

        #endregion
    }
}
